﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tile_Engine;

namespace AGrandVenture
{
    public class Sprite
    {
        #region Declarations
        public Texture2D Texture;
        public float spriteScale = 1f;
        public float drawDepth = 0.0f;

        private Vector2 worldLocation = Vector2.Zero;
        private Vector2 velocity = Vector2.Zero;

        private List<AnimationFrame> frames = new List<AnimationFrame>(); // all animation frames for sprite
        private int currentFrame; //frame currently being displayed

        private float rotation = 0.0f;

        private Color tintColor = Color.White;

        public bool Expired = false; // if true, sprite is not updated or drawn
        public bool Animate = true; // if false, sprite will not advance frame animations
        public bool AnimateWhenStopped = true; //if false, sprite will not advance frame animations if velocity is ZERO
        public bool playedAllFramesOnce = false; //if true, all AnimationFrames of the frames list have been played at least once

        public bool Collidable = true;
        public int CollisionRadius = 0;
        public int BoundingXPadding = 0;
        public int BoundingYPadding = 0;

        private Vector2 specifiedRelativeCentre = Vector2.Zero;
        #endregion

        #region Constructors
        public Sprite(Vector2 location, Texture2D texture, Rectangle initialFrame, Vector2 velocity, float depth)
        {
            WorldLocation = location;
            Texture = texture;
            Velocity = velocity;
            drawDepth = depth;

            frames.Add(new AnimationFrame(initialFrame, 0.0f, false, false, TintColor));
        }
        #endregion



        #region Drawing and Animation Properties
        public int FrameWidth
        {
            get { return frames[currentFrame].Rect.Width; }
        }

        public Color TintColor
        {
            get { return tintColor; }
            set { tintColor = value; foreach (AnimationFrame frame in frames) frame.TintColor = tintColor; }
        }

        public int FrameHeight
        {
            get { return frames[currentFrame].Rect.Height; }
        }

        public float Rotation
        {
            get { return rotation; }
            set { rotation = value % MathHelper.TwoPi; } // allows external code to increment value beyond a full rotation and still display correctly
        }

        public int CurrentFrame
        {
            get { return currentFrame; }
            set { currentFrame = (int)MathHelper.Clamp(value, 0, frames.Count - 1); } //ensures that currentframe number exists in frames
        }

        public bool PlayedAllFramesOnce
        {
            get { return playedAllFramesOnce; }
            set { playedAllFramesOnce = value; }
        }

        public Rectangle Source
        {
            get { return frames[currentFrame].Rect; } // returns rectangle for current frame
        }
        public float Scale
        {
            get { return spriteScale; }
            set { spriteScale = value; }
        }
        #endregion
        #region Positional Properties
        public Vector2 WorldLocation
        {
            get { return worldLocation; } // returns location of sprite relative to the world
            set { worldLocation = value; }
        }

        public Vector2 ScreenLocation
        {
            get { return Camera.WorldToScreen(worldLocation); } // returns location of sprite relative to the screen
        }

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }

        public Rectangle WorldRectangle // returns sprite Rectangle relative to the world
        {
            get
            {
                return new Rectangle(
                (int)worldLocation.X,
                (int)worldLocation.Y,
                FrameWidth,
                FrameHeight);
            }
        }

        public Rectangle ScreenRectangle
        {
            get { return Camera.WorldToScreen(WorldRectangle); } // returns sprite Rectangle relative to the screen
        }

        public Vector2 RelativeCentre
        {
            set { specifiedRelativeCentre = value; }
            get
            {
                if (specifiedRelativeCentre == Vector2.Zero)
                    return new Vector2(FrameWidth / 2, FrameHeight / 2);// centre of sprite relative to itself
                else 
                    return specifiedRelativeCentre;//Manually specified origin
            } 
        }

        public Vector2 WorldCentre
        {
            get { return worldLocation + RelativeCentre; }  // centre of sprite relative to the world
        }

        public Vector2 ScreenCentre
        {
            get { return Camera.WorldToScreen(worldLocation + RelativeCentre); } // centre of sprite relative to the screen
        }
        #endregion
        #region Collision Related Properties
        public Rectangle BoundingBoxRect
        {
            get
            {
                return new Rectangle(
                    (int)worldLocation.X + BoundingXPadding,
                    (int)worldLocation.Y + BoundingYPadding,
                    FrameWidth - (BoundingXPadding * 2),
                    FrameHeight - (BoundingYPadding * 2));
            }
        }
        #endregion

        #region Collision Detection Methods
        public bool IsBoxColliding(Rectangle OtherBox)
        {
            if ((Collidable) && (!Expired))
            {
                return BoundingBoxRect.Intersects(OtherBox);
            }
            else
            {
                return false;
            }
        }

        public bool IsCircleColliding(Vector2 otherCentre, float otherRadius)
        {
            if ((Collidable) && (!Expired))
            {
                if (Vector2.Distance(WorldCentre, otherCentre) < (CollisionRadius + otherRadius))
                    return true;
                else
                    return false;
            }
            else
            {
                return false;
            }
        }
        #endregion
        #region Animation Related Methods

        public void AddFrame(Rectangle frameRectangle)
        {
            frames.Add(new AnimationFrame(frameRectangle, 0.0f, false, false, TintColor));
        }

        public void AddFrame(Rectangle frameRectangle, float time)
        {
            frames.Add(new AnimationFrame(frameRectangle, time, false, false, TintColor));
        }

        public void AddFrame(Rectangle frameRectangle, float time, bool flipX, bool flipY)
        {
            frames.Add(new AnimationFrame(frameRectangle, time, flipX, flipY, TintColor));
        }

        public void ClearFrames()
        {
            frames = new List<AnimationFrame>();
            currentFrame = 0;
            playedAllFramesOnce = false;
        }

        public void FlipFramesX(bool flip)
        {
            foreach (AnimationFrame frame in frames)
            {
                frame.FlippedX = flip;
            }
        }

        public void FlipFramesY(bool flip)
        {
            foreach (AnimationFrame frame in frames)
            {
                frame.FlippedY = flip;
            }
        }

        public void ToggleFlipFramesX()
        {
            foreach (AnimationFrame frame in frames)
            {
                frame.FlippedX = !(frame.FlippedX);
            }
        }

        public void ToggleFlipFramesY()
        {
            foreach (AnimationFrame frame in frames)
            {
                frame.FlippedY = !(frame.FlippedY);
            }
        }

        public void RotateTo(Vector2 direction)
        {
            Rotation = (float)Math.Atan2(direction.Y, direction.X);
        }
        #endregion

        #region Update and Draw Methods
        public virtual void Update(GameTime gameTime)
        {
            if (!Expired)
            {
                float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
                frames[currentFrame].CurrentDisplayTime += elapsed;

                if (Animate)
                {
                    if (frames[currentFrame].CurrentDisplayTime >= frames[currentFrame].TotalDisplayTime)
                    {
                        if ((AnimateWhenStopped) || (velocity != Vector2.Zero))
                        {
                            currentFrame = (currentFrame + 1) % (frames.Count);
                            frames[currentFrame].CurrentDisplayTime = 0.0f;

                            if (currentFrame == 0)
                                playedAllFramesOnce = true;
                        }
                    }
                }

                worldLocation += (velocity * elapsed);
            }
        }

        public virtual void Draw(SpriteBatch spriteBatch)
        {
            if (!Expired)
            {
                if (Camera.ObjectIsVisible(WorldRectangle))
                {
                    SpriteEffects fx = ((frames[currentFrame].FlippedX && frames[currentFrame].FlippedY) ? SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically :
                                        frames[currentFrame].FlippedX ? SpriteEffects.FlipHorizontally :
                                        frames[currentFrame].FlippedY ? SpriteEffects.FlipVertically :
                                                                        SpriteEffects.None);
                    spriteBatch.Draw(
                        Texture,
                        ScreenCentre,
                        Source,
                        frames[currentFrame].TintColor,
                        rotation,
                        RelativeCentre,
                        spriteScale,
                        fx,
                        drawDepth);
                }
            }
        }

        public virtual void DrawShadow(SpriteBatch spriteBatch, Vector2 lightVector, double lightSize)
        {
            if (!Expired)
            {
                if (Camera.ObjectIsVisible(WorldRectangle))
                {
                    int sizeModifer = (int)Math.Pow((double)2, lightSize - 1);
                    Rectangle screenRectangle = ScreenRectangle;
                    screenRectangle.X += sizeModifer;
                    screenRectangle.Y += sizeModifer;

                    SpriteEffects fx = ((frames[currentFrame].FlippedX && frames[currentFrame].FlippedY) ? SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically :
                                        frames[currentFrame].FlippedX ? SpriteEffects.FlipHorizontally :
                                        frames[currentFrame].FlippedY ? SpriteEffects.FlipVertically :
                                                                        SpriteEffects.None);

                    spriteBatch.Draw(
                        Texture,
                        screenRectangle,
                        Source,
                        Color.Black,
                        rotation,
                        lightVector,
                        fx,
                        drawDepth);
                }
            }
        }
        #endregion
    }

    #region PrivateClasses
    class AnimationFrame
    {
        #region Declarations
        Rectangle rectangle;
        float timeToDisplay;
        bool flippedXAxis;
        bool flippedYAxis;
        Color frameTintColor;
        float timeForCurrentFrame;
        #endregion

        #region properties
        public Rectangle Rect
        {
            get { return rectangle; }
            set { rectangle = value; }
        }

        public float TotalDisplayTime
        {
            get { return timeToDisplay; }
            set { timeToDisplay = value; }
        }

        public float CurrentDisplayTime
        {
            get { return timeForCurrentFrame; }
            set { timeForCurrentFrame = value; }
        }

        public bool FlippedX
        {
            get { return flippedXAxis; }
            set { flippedXAxis = value; }
        }

        public bool FlippedY
        {
            get { return flippedYAxis; }
            set { flippedYAxis = value; }
        }

        public Color TintColor
        {
            get { return frameTintColor; }
            set { frameTintColor = value; }
        }
        #endregion

        #region constructor
        public AnimationFrame(Rectangle rect, Color tint)
        {
            rectangle = rect;
            timeToDisplay = 0.0f;
            flippedXAxis = false;
            flippedYAxis = false;
            frameTintColor = tint;
        }

        public AnimationFrame(Rectangle rect, float time, bool flippedX, bool flippedY, Color tint)
        {
            rectangle = rect;
            timeToDisplay = time;
            flippedXAxis = flippedX;
            flippedYAxis = flippedY;
            frameTintColor = tint;
        }
        #endregion
    }
    #endregion
}

